package com.pig4cloud.pigx.contract.executer.rate_settlement.job.canceler;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
import com.pig4cloud.pigx.common.core.util.R;
import com.pig4cloud.pigx.common.customize.contants.SystemConfigConstants;
import com.pig4cloud.pigx.common.customize.enums.CommonDictEnum;
import com.pig4cloud.pigx.common.customize.i18n.util.MessageUtils;
import com.pig4cloud.pigx.common.customize.mysql.utils.MysqlUtil;
import com.pig4cloud.pigx.common.customize.utils.CommonUtil;
import com.pig4cloud.pigx.common.seata.utils.SeataUtils;
import com.pig4cloud.pigx.contract.bo.RateSettlementJobBO;
import com.pig4cloud.pigx.contract.constant.I18nMessages;
import com.pig4cloud.pigx.contract.dto.AppContractMarginChangeDTO;
import com.pig4cloud.pigx.contract.entity.ContractPosition;
import com.pig4cloud.pigx.contract.entity.ContractPositionMarginDetail;
import com.pig4cloud.pigx.contract.enums.ContractPositionEnum;
import com.pig4cloud.pigx.contract.enums.ContractPositionMarginDetailEnum;
import com.pig4cloud.pigx.contract.executer.order.supporter.OrderCalculator;
import com.pig4cloud.pigx.contract.executer.position.supporter.PositionCalculator;
import com.pig4cloud.pigx.contract.mapper.ContractPositionMapper;
import com.pig4cloud.pigx.contract.mongo.entity.ContractSymbolMongo;
import com.pig4cloud.pigx.contract.service.ContractPositionService;
import com.pig4cloud.pigx.contract.utils.ContractUtils;
import com.pig4cloud.pigx.wallet.api.feign.WalletContractAccountControllerFeign;
import com.pig4cloud.pigx.wallet.dto.WalletContractAccountChangeDTO;
import com.pig4cloud.pigx.wallet.entity.ContractAccount;
import com.pig4cloud.pigx.wallet.enums.WalletContractAccountDetailEnum;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Objects;

/**
 * 抽象资金费率任务处理器
 *
 */
@Slf4j
@Component
@AllArgsConstructor
public class RateSettlementJobCanceler {


    private final ContractUtils contractUtils;

    private final ContractUtils utils;

    private final ContractPositionMapper positionMapper;

    private final WalletContractAccountControllerFeign controllerFeign;

    private final ContractPositionService contractPositionService;

    private final CommonUtil commonUtil;
    private final MysqlUtil mysqlUtil;

    private final RedisTemplate<String, Object> redis;

    private final static String JOB_NUM_REDIS_KEY = "rate_settlement_job_complete_num";

    private final static String ALL_AMOUNT_REDIS_KEY = "rate_settlement_job_all_amount";

    private final static String AMOUNT_REDIS_KEY = "rate_settlement_job_amount";


    /**
     * 扣钱
     */
    public void remove(RateSettlementJobBO bo) {
        // 需要扣除/增加的费用
        BigDecimal multiply = bo.getMultiply();
        // 本应扣除
        BigDecimal allAmount = BigDecimal.ZERO;
        // 实际扣除
        BigDecimal amount = BigDecimal.ZERO;
        // 是否操作了钱包
        boolean isWallet = false;
        try {
            allAmount = allAmount.add(multiply);
            // 获取资金详情
            ContractAccount contractAccount = controllerFeign.contractInfo(new WalletContractAccountChangeDTO() {{
                setUserId(bo.getUserId());
            }}, SecurityConstants.FROM_IN).getData();
            // 重新查询仓位状态
            ContractPosition selectOne = positionMapper.selectOne(new QueryWrapper<ContractPosition>() {{
                eq("id", bo.getPositionId());
                last("for update");
            }});
            // 组装划转请求体
            WalletContractAccountChangeDTO feign = getFeign(selectOne, bo.getFeeRate());
            // 开启分布式事务
            SeataUtils.startGlobalTx();
            // 开启本地事务
            mysqlUtil.startLocalTx();
            // 逐仓
            if (selectOne.getMode().equals(ContractPositionEnum.Mode.ISOLATED.getValue())) {
                // 计算 标准维持保证金 (开仓均价 * 当前持仓数量) / 杠杆倍数
                BigDecimal divide = selectOne.getOpenAvgPrice()
                        .multiply(selectOne.getNowNum())
                        .divide(new BigDecimal(selectOne.getMultiple()), SystemConfigConstants.amountScale, SystemConfigConstants.mainCurRoundingMode);
                // 如果保证金不会被扣除到维持保证金以下,扣除全额资金费率
                if (selectOne.getMargin().subtract(multiply).compareTo(divide) > 0) {
                    AppContractMarginChangeDTO dto = new AppContractMarginChangeDTO();
                    dto.setAmount(CommonUtil.setUSDTScale(multiply));
                    dto.setCalculationType(CommonDictEnum.CalculationType.MINUS.getValue());
                    dto.setPositionId(selectOne.getId());
                    this.marginChange(dto);
                    amount = amount.add(multiply);
                } else {
                    // 如果本次扣除保证金使用户无法维持,则扣除可扣除部分,剩余部分则去尝试扣除钱包
                    if (selectOne.getMargin().compareTo(divide) > 0) {
                        AppContractMarginChangeDTO dto = new AppContractMarginChangeDTO();
                        // 保证金扣除部分
                        BigDecimal subtract = selectOne.getMargin().subtract(divide);
                        dto.setAmount(CommonUtil.setUSDTScale(subtract));
                        dto.setCalculationType(CommonDictEnum.CalculationType.MINUS.getValue());
                        dto.setPositionId(selectOne.getId());
                        this.marginChange(dto);
                        amount = amount.add(subtract);
                        // 尝试扣除钱包余额
                        BigDecimal bigDecimal = multiply.subtract(subtract);
                        R<BigDecimal> r = controllerFeign.change(new ArrayList<WalletContractAccountChangeDTO>() {{
                            feign.setAmount(CommonUtil.setUSDTScale(bigDecimal.negate()));
                            feign.setOperationType(WalletContractAccountDetailEnum.OperationType.MINUS);
                            add(feign);
                        }}, SecurityConstants.FROM_IN);
                        if (r.getData() != null && r.getData().compareTo(BigDecimal.ZERO) > 0) {
                            amount = amount.add(r.getData());
                            isWallet = true;
                        }
                    } else {
                        // 用户保证金一点也扣不出来，则查询钱包余额
                        BigDecimal wallet = contractAccount.getWalletBalance();
                        if (wallet.subtract(multiply).compareTo(multiply) > 0) {
                            R<BigDecimal> r = controllerFeign.change(new ArrayList<WalletContractAccountChangeDTO>() {{
                                feign.setAmount(CommonUtil.setUSDTScale(multiply.negate()));
                                feign.setOperationType(WalletContractAccountDetailEnum.OperationType.MINUS);
                                add(feign);
                            }}, SecurityConstants.FROM_IN);
                            if (r.getData() != null) {
                                amount = amount.add(r.getData());
                                isWallet = true;
                            }
                        } else {
                            // 如果钱包钱不够,则有多少扣多少
                            if (wallet.compareTo(new BigDecimal("0")) > 0) {
                                R<BigDecimal> r = controllerFeign.change(new ArrayList<WalletContractAccountChangeDTO>() {{
                                    feign.setAmount(CommonUtil.setUSDTScale(wallet.negate()));
                                    feign.setOperationType(WalletContractAccountDetailEnum.OperationType.MINUS);
                                    add(feign);
                                }}, SecurityConstants.FROM_IN);
                                if (r.getData() != null) {
                                    amount = amount.add(r.getData());
                                    isWallet = true;
                                }
                            }
                        }
                    }
                }
            }
            // 全仓
            else {
                // 直接扣钱包
                BigDecimal wallet = contractAccount.getWalletBalance();
                if (wallet.subtract(multiply).compareTo(multiply) > 0) {
                    R<BigDecimal> r = controllerFeign.change(new ArrayList<WalletContractAccountChangeDTO>() {{
                        feign.setAmount(CommonUtil.setUSDTScale(multiply.negate()));
                        feign.setOperationType(WalletContractAccountDetailEnum.OperationType.MINUS);
                        add(feign);
                    }}, SecurityConstants.FROM_IN);
                    if (r.getData() != null && r.getData().compareTo(BigDecimal.ZERO) > 0) {
                        amount = amount.add(r.getData());
                        isWallet = true;
                    }
                } else {
                    // 如果钱包钱不够,则有多少扣多少
                    if (wallet.compareTo(BigDecimal.ZERO) > 0) {
                        R<BigDecimal> r = controllerFeign.change(new ArrayList<WalletContractAccountChangeDTO>() {{
                            feign.setAmount(CommonUtil.setUSDTScale(wallet.negate()));
                            feign.setOperationType(WalletContractAccountDetailEnum.OperationType.MINUS);
                            add(feign);
                        }}, SecurityConstants.FROM_IN);
                        if (r.getData() != null && r.getData().compareTo(BigDecimal.ZERO) > 0) {
                            amount = amount.add(r.getData());
                            isWallet = true;
                        }
                    }
                }
            }
            // 扣除金额等字段，需要全部转正数
            if (allAmount.signum() == -1) {
                allAmount = allAmount.negate();
            }
            if (amount.signum() == -1) {
                amount = amount.negate();
            }
            // 如果本次有扣除钱包金额业务,则发送重新计算强平价消息
            if (isWallet) {
                utils.sendPositionCrossCalculate(bo.getUserId());
            }
            // redis 累计应扣金额
            Object redisAllAmount = redis.opsForValue().get(ALL_AMOUNT_REDIS_KEY);
            if (redisAllAmount != null) {
                redis.opsForValue().set(ALL_AMOUNT_REDIS_KEY
                        , new BigDecimal(redisAllAmount.toString()).add(allAmount));
            } else {
                redis.opsForValue().set(ALL_AMOUNT_REDIS_KEY, allAmount);
            }
            // redis 累计实际扣除
            Object redisAmount = redis.opsForValue().get(AMOUNT_REDIS_KEY);
            if (redisAmount != null) {
                redis.opsForValue().set(AMOUNT_REDIS_KEY
                        , new BigDecimal(redisAmount.toString()).add(amount));
            } else {
                redis.opsForValue().set(AMOUNT_REDIS_KEY, amount);
            }
            // 提交本地事务
            mysqlUtil.commitLocalTx();
            // 提交分布式事务
            SeataUtils.commitGlobalTx();
        } catch (Exception e) {
            // 回滚本地事务
            mysqlUtil.rollbackLocalTx();
            // 回滚分布式事务
            SeataUtils.rollbackGlobalTx();
        } finally {
            // 扣钱操作需要在执行成功后对redis做回执递增
            Object integer = redis.opsForValue().get(JOB_NUM_REDIS_KEY);
            if (integer != null) {
                redis.opsForValue().set(JOB_NUM_REDIS_KEY, (Integer) integer + 1);
            } else {
                redis.opsForValue().set(JOB_NUM_REDIS_KEY, 1);
            }
        }
    }

    /**
     * 发钱
     */
    public void add(RateSettlementJobBO bo) {
        try {
            // 开启分布式事务
            SeataUtils.startGlobalTx();
            ContractPosition selectOne = positionMapper.selectOne(new QueryWrapper<ContractPosition>() {{
                eq("id", bo.getPositionId());
            }});
            // 组装划转请求体
            WalletContractAccountChangeDTO feign = getFeign(selectOne, bo.getFeeRate());
            // 应获取金额 * 实际上缴比例 = 最终收益
            controllerFeign.change(new ArrayList<WalletContractAccountChangeDTO>() {{
                feign.setAmount(bo.getMultiply().multiply(bo.getSaveProportion()));
                feign.setOperationType(WalletContractAccountDetailEnum.OperationType.ADD);
                add(feign);
            }}, SecurityConstants.FROM_IN);
            // 提交分布式事务
            SeataUtils.commitGlobalTx();
            // 发送消息重新计算强平价
            utils.sendPositionCrossCalculate(bo.getUserId());
        } catch (Exception e) {
            // 回滚分布式事务
            SeataUtils.rollbackGlobalTx();
        }
    }

    /**
     * 划转请求参数拼接
     *
     * @param position 仓位信息
     * @param decimal  资金费率
     * @return 划转请求体
     */
    public WalletContractAccountChangeDTO getFeign(ContractPosition position, BigDecimal decimal) {
        // 组装划转请求体
        WalletContractAccountChangeDTO contractFeign = new WalletContractAccountChangeDTO();
        contractFeign.setUserId(position.getUserId());
        contractFeign.setSymbolId(position.getSymbolId());
        contractFeign.setSymbolName(position.getSymbolName());
        contractFeign.setBusinessType(WalletContractAccountDetailEnum.BusinessType.FUNDING_COSTS);
        contractFeign.setFeeRate(decimal);
        return contractFeign;
    }

    /**
     * 逐仓 调整仓位保证金
     *
     * @param dto 调整逐仓仓位保证金DTO
     */
    public void marginChange(AppContractMarginChangeDTO dto) {
        // 查询仓位信息，并锁住
        ContractPosition condition = new ContractPosition();
        condition.setId(dto.getPositionId());
        ContractPosition position = contractUtils.getOneCurrentPosition(condition, true);
        ContractUtils.checkCurrentPosition(position, false);

        if (Objects.equals(CommonDictEnum.YesOrNo.YES.getValue(), position.getNeedLiquidate())) {
            // 仓位强平中
            throw new RuntimeException(MessageUtils.get(I18nMessages.POSITION_IS_STRONG_IN_FLAT));
        }

        // 判断是否是逐仓
        if (!Objects.equals(ContractPositionEnum.Mode.ISOLATED.getValue(), position.getMode())) {
            // 异常操作
            throw new RuntimeException(MessageUtils.get(I18nMessages.ABNORMAL_OPERATION));
        }

        // 查询币对信息
        ContractSymbolMongo symbol = contractUtils.getSymbolById(position.getSymbolId());
        ContractUtils.checkSymbol(symbol);

        // JVM根据计算类型修改仓位保证金
        if (Objects.equals(CommonDictEnum.CalculationType.ADD.getValue(), dto.getCalculationType())) {
            // 增加
            position.setMargin(position.getMargin().add(dto.getAmount()));
        } else {
            // 减少
            position.setMargin(position.getMargin().subtract(dto.getAmount()));

            // 计算标准保证金
            BigDecimal positionValue = OrderCalculator.getOrderValue(position.getOpenAvgPrice(), position.getNowNum());
            BigDecimal standardMargin = PositionCalculator.getStandardMargin(positionValue, position.getMultiple());
            if (position.getMargin().compareTo(standardMargin) < 0) {
                // 超过可操作仓位保证金上限
                throw new RuntimeException(MessageUtils.get(I18nMessages.EXCEED_THE_LIMIT_OF_OPERABLE_POSITION_MARGIN));
            }
        }

        // JVM重新计算强平价和是否需要强平
        PositionCalculator.calculateLiquidateIsolated(position, symbol);
        // 判断是否需要强平
        if (Objects.equals(CommonDictEnum.YesOrNo.YES.getValue(), position.getNeedLiquidate())) {
            // 将导致仓位强平，请修改调整金额
            throw new RuntimeException(MessageUtils.get(I18nMessages.POSITION_WILL_BE_STRONGLY_CLOSED));
        }

        // 根据加/减获得操作类型
        WalletContractAccountDetailEnum.OperationType operationType = Objects.equals(CommonDictEnum.CalculationType.ADD.getValue(), dto.getCalculationType()) ?
                WalletContractAccountDetailEnum.OperationType.ADD
                : WalletContractAccountDetailEnum.OperationType.MINUS;

        // 修改钱包余额
        WalletContractAccountChangeDTO param = new WalletContractAccountChangeDTO(
                position.getUserId(),
                dto.getAmount(),
                null,
                position.getSymbolId(),
                position.getSymbolName(),
                WalletContractAccountDetailEnum.BusinessType.POSITION_MARGIN,
                operationType,
                false);
        R<?> r = contractUtils.changeAsset(Collections.singletonList(param));
        if (!r.getIsSuccess()) {
            throw new RuntimeException(r.getMsg());
        }

        // 修改仓位信息
        if (positionMapper.updateById(position) != 1) {
            throw new RuntimeException(MessageUtils.get(I18nMessages.FAILED_TO_MODIFY_POSITION_INFORMATION));
        }

        // 发送重算全仓强平价消息
        contractUtils.sendPositionCrossCalculate(position.getUserId());

        // 发送保证金明细消息
        ContractPositionMarginDetail detail = new ContractPositionMarginDetail();
        detail.setPositionId(position.getId());
        detail.setMargin(dto.getAmount());
        detail.setBusinessType(ContractPositionMarginDetailEnum.BusinessType.CHANGE_MARGIN.getValue());
        // 根据保证金操作类型获取保证金明细操作类型
        ContractPositionMarginDetailEnum.OperationType marginOperationType = Objects.equals(CommonDictEnum.CalculationType.ADD.getValue(), dto.getCalculationType()) ?
                ContractPositionMarginDetailEnum.OperationType.ADD
                : ContractPositionMarginDetailEnum.OperationType.MINUS;
        detail.setOperationType(marginOperationType.getValue());
        detail.setLiquidatePrice(position.getLiquidatePrice());
        contractUtils.sendSavePositionMarginDetailMq(Collections.singletonList(detail));
    }

}
